package cn.seaboot.excel.html;

import cn.seaboot.commons.core.CommonUtils;
import cn.seaboot.excel.ExcelCell;
import cn.seaboot.excel.ExcelStyle;
import cn.seaboot.html.HtmlReader;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFColor;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.io.Writer;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 * 读取工作表的内容，并转为 html 输出
 * <p>
 * 优势是可以读取更多 excel 细节
 * <p>
 * 替代方案：使用前端的 sheetjs 库
 *
 * @author Mr.css
 * @version 2025-06-26 14:37
 */
public class SheetReader implements HtmlReader<Sheet> {

    /**
     * 结果写出
     */
    private final Writer writer;

    /**
     * 宽度缩放
     * <p>
     * Excel 中的单位比较特殊（字符单位，正常要除以 256，个人推荐值为 32，这与屏幕分辨率有关）
     */
    private int widthScale = 32;

    /**
     * 列宽参考列
     * <p>
     * 选择数据比较多的一行就好，所有单元格会参考当前行的列宽设置
     * <p>
     * 有些 excel 会有奇怪的习惯，从第二行开始写入数据，这时候需要调整当前值
     */
    private int widthReferenceRow = 0;
    /**
     * 单元格 class 名称前缀
     */
    private String prefix = "td-font-";

    public SheetReader(Writer writer) {
        this.writer = writer;
    }

    @Override
    public void read(Sheet sheet) throws IOException {
        // 单元格合并信息
        List<CellRangeAddress> list = sheet.getMergedRegions();
        LinkedList<CellRangeAddress> addresses = new LinkedList<>(list);

        writer.write("<table>\r\n");

        // colgroup
        this.writeColumnGroup(sheet, sheet.getRow(2));

        // tbody
        // 起始行号、列号
        writer.write("<tbody>\r\n");
        // 注意这种写法会过滤所有空行
        for (Row row : sheet) {
            writer.write("<tr>\r\n");
            int i = 0;
            int n = row.getLastCellNum();
            while (i < n) {
                Cell cell = row.getCell(i);
                if (cell == null) {
                    // empty cell
                    writer.write("<td></td>\r\n");
                    i++;
                } else {
                    // cell with content
                    i = parseCell(cell, addresses);
                }
            }
            writer.write("</tr>\r\n");
        }
        writer.write("</tbody>\r\n");
        writer.write("</table>\r\n");
    }

    /**
     * 处理单元格
     *
     * @param cell      单元格
     * @param addresses 单元格合并信息
     * @return 下一个单元格 id
     * @throws IOException -
     */
    public int parseCell(@NotNull Cell cell, LinkedList<CellRangeAddress> addresses) throws IOException {
        int rn = cell.getRowIndex(), cn = cell.getColumnIndex();
        // start <td>
        CellRangeAddress address = getMergedAddress(rn, cn, addresses);
        if (address != null) {
            // 单元格合并
            if (address.getFirstRow() == rn && address.getFirstColumn() == cn) {
                writer.write("<td");

                // 计算合并的行数和列数
                if (address.getLastRow() > rn) {
                    int rowspan = address.getLastRow() - address.getFirstRow();
                    writer.write(" rowspan=\"" + (rowspan + 1) + "\"");
                }
                if (address.getLastColumn() > cn) {
                    int colspan = address.getLastColumn() - address.getFirstColumn();
                    writer.write(" colspan=\"" + (colspan + 1) + "\"");
                    // 将指针移到最后一列
                    cn = address.getLastColumn();
                }
            } else {
                // 被合并的单元格
                return address.getLastColumn() + 1;
            }
        } else {
            // 普通单元格
            writer.write("<td");
        }


        XSSFCellStyle style = (XSSFCellStyle) cell.getCellStyle();

        // style start ----------------------------------------

        // cell-width
        writer.write(" style=\"");

        // background-color
        XSSFColor bg = style.getFillForegroundColorColor();
        if (bg != null) {
            writer.write("background-color: " + ExcelStyle.convertHexString(bg) + "; ");
        }

        // text-align
        if (style.getAlignment() == HorizontalAlignment.CENTER) {
            writer.write("text-align: center;");
        } else if (style.getAlignment() == HorizontalAlignment.RIGHT) {
            writer.write("text-align: right;");
        }

        writer.write("\"");
        // style end ----------------------------------------

        // class
        int idx = style.getFontIndex();
        writer.write(" class=\"" + prefix + idx + "\"");

        // close <td>
        writer.write(">");

        // content
        String value = ExcelCell.readString(cell);
        writer.write(CommonUtils.nvl(value, ""));

        // end </td>
        writer.write("</td>\r\n");

        cn++;
        return cn;
    }


    /**
     * 写入列分组信息
     * <p>
     * 因为是以第一行数据做参照，需要确保第一行的列数，不会小于后续其它行。
     * <p>
     * 潜在问题：超出第一行的单元格，列宽会自动分配
     * <p>
     * 这个做法能能提示代码性能，并且大幅度减少生成的 html 大小。
     *
     * <pre><code>
     *     <colgroup>
     *       <col span="2" style="background-color: red;width: 160px;">
     *       <col style="background-color: blue;">
     *     </colgroup>
     * </code></pre>
     *
     * @param sheet 工作表
     * @param first 第一行
     * @throws IOException -
     */
    public void writeColumnGroup(Sheet sheet, Row first) throws IOException {
        writer.write("<colgroup>\r\n");
        int n = first.getLastCellNum();
        for (int i = 0; i < n; i++) {
            writer.write("<col style=\"width: " + sheet.getColumnWidth(i) / widthScale + "px;\"/>\r\n");
        }
        writer.write("</colgroup>\r\n");
    }

    /**
     * 判断是否属于合并的单元格区域
     *
     * @param row       行号
     * @param col       列号
     * @param addresses 合并信息
     * @return 命中的范围信息
     */
    public CellRangeAddress getMergedAddress(int row, int col, LinkedList<CellRangeAddress> addresses) {
        Iterator<CellRangeAddress> ite = addresses.iterator();
        while (ite.hasNext()) {
            CellRangeAddress address = ite.next();
            if (address.getLastRow() < row) {
                // 清理解析过的合并区域，提升性能
                ite.remove();
            } else if (address.getFirstRow() <= row && address.getFirstColumn() <= col && address.getLastColumn() >= col) {
                // 处于区域范围内
                return address;
            }
        }
        return null;
    }

    public int getWidthScale() {
        return widthScale;
    }

    public void setWidthScale(int widthScale) {
        this.widthScale = widthScale;
    }

    public int getWidthReferenceRow() {
        return widthReferenceRow;
    }

    public void setWidthReferenceRow(int widthReferenceRow) {
        this.widthReferenceRow = widthReferenceRow;
    }

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }
}
